/*
 * Copyright (c) 2022-2022 Huawei Technologies Co., Ltd. All rights reserved.
 * Copyright (c) 2022-2022 Huawei Device Co., Ltd. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 *    conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 *    of conditions and the following disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 *    to endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "los_user_container.h"
#include "errno.h"
#include "ctype.h"
#include "los_config.h"
#include "los_memory.h"
#include "proc_fs.h"
#include "user_copy.h"
#include "los_seq_buf.h"
#include "capability_type.h"
#include "capability_api.h"

#define DEFAULT_OVERFLOWUID    65534
#define DEFAULT_OVERFLOWGID    65534
#define LEVEL_MAX 3

#ifdef LOSCFG_USER_CONTAINER
extern struct ContainerConfig g_containerConfig;
UINT32 g_currentUserContainerNum = 1;

UserContainer g_rootUserContainer = {
    .count = ATOMIC_INIT(3),
    .level = 0,
    .owner = 0,
    .group = 0,
    .parent = NULL,
    .uidMap = {
        .extentCount = 1,
        {
            .extent[0] = {
            .first = 0,
            .lowerFirst = 0,
            .count = 4294967295U,
            },
        },
    },
    .gidMap = {
        .extentCount = 1,
        {
            .extent[0] = {
            .first = 0,
            .lowerFirst = 0,
            .count = 4294967295U,
            },
        },
    },
};

UserContainer *CreateUserContainer(Credentials *newCredentials)
{
    UserContainer *newUserContainer = NULL;
    UserContainer *parentUserContainer= GetUserContainer(newCredentials->userContainer);
    UINT32 intSave;

    if (g_currentUserContainerNum > g_containerConfig.MaxUserContainer)
    {
        PRINT_ERR("CreateUserContainer failed, reach to max container config.\n");
        return NULL;
    }

    if (parentUserContainer->level > LEVEL_MAX) {
        return NULL;
    }

    if ((newCredentials->euid == -1) || (newCredentials->egid == -1)) {
        return NULL;
    }

    UINT32 size = sizeof(UserContainer);
    newUserContainer = LOS_MemAlloc(m_aucSysMem1, size);

    if (newUserContainer == NULL) {
        PRINT_ERR("CreateUserContainer failed to malloc memory.\n");
        return NULL;
    }

    SCHEDULER_LOCK(intSave);
    g_currentUserContainerNum += 1;
    SCHEDULER_UNLOCK(intSave);

    atomic_set(&newUserContainer->count, 1);
    ContainerAllocVnum(&(newUserContainer->containerBase.vnum));

    newUserContainer->parent = parentUserContainer;
    newUserContainer->level = parentUserContainer->level + 1;
    newUserContainer->owner = newCredentials->euid;
    newUserContainer->group = newCredentials->egid;
    newUserContainer->uidMap.extentCount = 0;
    newUserContainer->gidMap.extentCount = 0;

    return newUserContainer;
}

void FreeUserContainer(UserContainer *userContainer)
{
    UserContainer *parent = NULL;
    UINT32 intSave;

    do {
        parent = userContainer->parent;
        LOS_MemFree(m_aucSysMem1, userContainer);
        userContainer = parent;

        SCHEDULER_LOCK(intSave);
        g_currentUserContainerNum -= 1;
        SCHEDULER_UNLOCK(intSave);
    } while (parent && atomic_dec_and_test(&parent->count));
}

static UidGidExtent *MapIdUpBase(UINT32 extents, UidGidMap *map, UINT32 id)
{
    UINT32 idx;
    UINT32 first;
    UINT32 last;

    for (idx = 0; idx < extents; idx++) {
        first = map->extent[idx].lowerFirst;
        last = first + map->extent[idx].count - 1;
        if (id >= first && id <= last) {
            return &map->extent[idx];
        }
    }
    return NULL;
}

static UINT32 MapIdUp(UidGidMap *map, UINT32 id)
{
    UidGidExtent *extent = NULL;
    UINT32 extents = map->extentCount;

    if (extents > UID_GID_MAP_MAX_EXTENTS) {
        return (UINT32)-1;
    }
    extent = MapIdUpBase(extents, map, id);

    if (extent) {
        id = (id - extent->lowerFirst) + extent->first;
    } else {
        id = (UINT32)-1;
    }
    return id;
}

UINT32 FromKuid(UserContainer *userContainer, UINT32 kuid)
{
    return MapIdUp(&userContainer->uidMap, kuid);
}

UINT32 FromKgid(UserContainer *userContainer, UINT32 kgid)
{
    return MapIdUp(&userContainer->gidMap, kgid);
}

UINT32 FromKuidMunged(UserContainer *userContainer, UINT32 kuid)
{
    UINT32 uid;
    uid = FromKuid(userContainer, kuid);

    if (uid == (UINT32) -1) {
        uid = DEFAULT_OVERFLOWUID;
    }
    return uid;
}

UINT32 FromKgidMunged(UserContainer *userContainer, UINT32 kgid)
{
    UINT32 gid;
    gid = FromKgid(userContainer, kgid);

    if (gid == (UINT32) -1)
        gid = DEFAULT_OVERFLOWGID;
    return gid;
}

static UidGidExtent *MapIdRangeDownBase(UINT32 extents, UidGidMap *map, UINT32 id, UINT32 count)
{
    UINT32 idx;
    UINT32 first;
    UINT32 last;
    UINT32 id2;

    id2 = id + count - 1;

    for (idx = 0; idx < extents; idx++) {
        first = map->extent[idx].first;
        last = first + map->extent[idx].count - 1;
        if ((id >= first && id <= last) && (id2 >= first && id2 <= last)) {
            return &map->extent[idx];
        }
    }
    return NULL;
}

static UINT32 MapIdRangeDown(UidGidMap *map, UINT32 id, UINT32 count)
{
    UidGidExtent *extent;
    UINT32 extents = map->extentCount;

    if (extents > UID_GID_MAP_MAX_EXTENTS) {
        return (UINT32)-1;
    }
    extent = MapIdRangeDownBase(extents, map, id, count);

    if (extent) {
        id = (id - extent->first) + extent->lowerFirst;
    } else {
        id = (UINT32) -1;
    }

    return id;
}

static UINT32 MapIdDown(UidGidMap *map, UINT32 id)
{
    return MapIdRangeDown(map, id, 1);
}

UINT32 MakeKuid(UserContainer *userContainer, UINT32 uid)
{
    return MapIdDown(&userContainer->uidMap, uid);
}

UINT32 MakeKgid(UserContainer *userContainer, UINT32 gid)
{
    return MapIdDown(&userContainer->gidMap, gid);
}

static int InsertExtent(UidGidMap *idMap, UidGidExtent *extent)
{
    UidGidExtent *dest;

    if (idMap->extentCount > UID_GID_MAP_MAX_EXTENTS) {
        return -EINVAL;
    }
    dest = &idMap->extent[idMap->extentCount];

    *dest = *extent;
    idMap->extentCount++;
    return 0;
}

static bool MappingsOverlap(UidGidMap *idMap, UidGidExtent *extent)
{
    UINT32 upperFirst;
    UINT32 lowerFirst;
    UINT32 upperLast;
    UINT32 lowerLast;
    unsigned idx;

    upperFirst = extent->first;
    lowerFirst = extent->lowerFirst;
    upperLast = upperFirst + extent->count - 1;
    lowerLast = lowerFirst + extent->count - 1;

    for (idx = 0; idx < idMap->extentCount; idx++) {
        UidGidExtent *prev;
        UINT32 prevUpperFirst;
        UINT32 prevLowerFirst;
        UINT32 prevUpperLast;
        UINT32 prevLowerLast;
        
        if (idMap->extentCount <= UID_GID_MAP_MAX_EXTENTS) {
            prev = &idMap->extent[idx];
        } else {
            return true;
        }

        prevUpperFirst = prev->first;
        prevLowerFirst = prev->lowerFirst;
        prevUpperLast = prevUpperFirst + prev->count - 1;
        prevLowerLast = prevLowerFirst + prev->count - 1;

        if ((prevUpperFirst <= upperLast) && (prevUpperLast >= upperFirst)) {
            return true;
        }

        if ((prevLowerFirst <= lowerLast) && (prevLowerLast >= lowerFirst)) {
            return true;
        }
    }
    return false;
}

static char *SkipSpaces(const char *str)
{
    while (isspace(*str)) {
        ++str;
    }

    return (char *)str;
}

static unsigned long SimpleStrtoul(const char *str, char **endp, unsigned int base)
{
    unsigned long result = 0;
    unsigned long value;

    if (*str == '0') {
        str++;
        if ((*str == 'x') && isxdigit(str[1])) {
            base = 16;
            str++;
        }
        if (!base) {
            base = 8;
        }
    }
    if (!base) {
        base = 10;
    }
    while (isxdigit(*str) && (value = isdigit(*str) ? *str-'0' : (islower(*str)
        ? toupper(*str) : *str)-'A'+10) < base) {
        result = result * base + value;
        str++;
    }
    if (endp) {
        *endp = (char *)str;
    }
    return result;
}

static int ParseUserData(char *kbuf, UidGidExtent* extent, UidGidMap *newMap)
{
    int ret = -EINVAL;
    char *pos = NULL;
    char *nextLine = NULL;

    for (pos = kbuf; pos; pos = nextLine) {
        nextLine = strchr(pos, '\n');
        if (nextLine) {
            *nextLine = '\0';
            nextLine++;
            if (*nextLine == '\0') {
                nextLine = NULL;
            }
        }

        pos = SkipSpaces(pos);
        extent->first = SimpleStrtoul(pos, &pos, 10);
        if (!isspace(*pos)) {
            return ret;
        }

        pos = SkipSpaces(pos);
        extent->lowerFirst = SimpleStrtoul(pos, &pos, 10);
        if (!isspace(*pos)) {
            return ret;
        }

        pos = SkipSpaces(pos);
        extent->count = SimpleStrtoul(pos, &pos, 10);
        if (*pos && !isspace(*pos)) {
            return ret;
        }

        pos = SkipSpaces(pos);
        if (*pos != '\0') {
            return ret;
        }

        if ((extent->first == (UINT32) -1) || (extent->lowerFirst == (UINT32) -1)) {
            return ret;
        }

        if ((extent->first + extent->count) <= extent->first) {
            return ret;
        }

        if ((extent->lowerFirst + extent->count) <= extent->lowerFirst) {
            return ret;
        }

        if (MappingsOverlap(newMap, extent)) {
            return ret;
        }

        if ((newMap->extentCount + 1) == UID_GID_MAP_MAX_EXTENTS && (nextLine != NULL)) {
            return ret;
        }

        ret = InsertExtent(newMap, extent);
        if (ret < 0) {
            return ret;
        }
        ret = 0;
    }

    if (newMap->extentCount == 0) {
        return -EINVAL;
    }

    return ret;
}

static void *MemdupUserNul(const void *src, size_t len)
{
    char *des = NULL;
    if (len < 0) {
        return NULL;
    }
    des = LOS_MemAlloc(OS_SYS_MEM_ADDR, len + 1);
    if (des == NULL) {
        return NULL;
    }

    if (LOS_ArchCopyFromUser(des, src, len) != 0) {
        (void)LOS_MemFree(OS_SYS_MEM_ADDR, des);
        return NULL;
    }

    des[len] = '\0';
    return des;
}

static int ParentMapIdRange(UidGidMap *newMap, UidGidMap *parentMap)
{
    UINT32 idx;
    int ret = -EPERM;
    for (idx = 0; idx < newMap->extentCount; idx++) {
        UidGidExtent *extent;
        UINT32 lowerFirst;

        if (newMap->extentCount > UID_GID_MAP_MAX_EXTENTS) {
            return ret;
        }
        extent = &newMap->extent[idx];

        lowerFirst = MapIdRangeDown(parentMap, extent->lowerFirst, extent->count);
        if (lowerFirst == (UINT32) -1) {
            return ret;
        }

        extent->lowerFirst = lowerFirst;
    }
    return 0;
}

static int MapWrite(struct ProcFile *fp, const char *buf, size_t count,
    int capSetid, UidGidMap *map, UidGidMap *parentMap)
{
    UidGidMap newMap;
    UidGidExtent extent;
    char *kbuf = NULL;
    int ret;
    unsigned int intSave;

    if (count >= PAGE_SIZE) {
        return -EINVAL;
    }

    kbuf = MemdupUserNul(buf, count);
    if (kbuf == NULL) {
        return -EINVAL;
    }

    SCHEDULER_LOCK(intSave);
    memset(&newMap, 0, sizeof(UidGidMap));

    ret = -EPERM;
    if (map->extentCount != 0) {
        goto out;
    }

    if (!IsCapPermit(capSetid)) {
        goto out;
    }

    ret = ParseUserData(kbuf, &extent, &newMap);
    if (ret < 0) {
        goto out;
    }

    ret = ParentMapIdRange(&newMap, parentMap);
    if (ret < 0) {
        goto out;
    }

    if (newMap.extentCount <= UID_GID_MAP_MAX_EXTENTS) {
        memcpy(map->extent, newMap.extent, newMap.extentCount * sizeof(newMap.extent[0]));
    }
    map->extentCount = newMap.extentCount;

    ret = count;
    fp->sbuf->buf = kbuf;
    SCHEDULER_UNLOCK(intSave);
    return ret;

out:
    SCHEDULER_UNLOCK(intSave);
    (void)LOS_MemFree(OS_SYS_MEM_ADDR, kbuf);
    return ret;
}

ssize_t ProcUidMapWrite(struct ProcFile *file, const char *buf,
    size_t size, loff_t *ppos)
{
    struct SeqBuf *seq = file->sbuf;
    UserContainer *userContainer = seq->private;

    if (userContainer != NULL && userContainer->parent == NULL) {
        return -EPERM;
    }

    return MapWrite(file, buf, size, CAP_SETUID, &userContainer->uidMap,
        &userContainer->parent->uidMap);
}

ssize_t ProcGidMapWrite(struct ProcFile *file, const char *buf,
    size_t size, loff_t *ppos)
{
    struct SeqBuf *seq = file->sbuf;
    UserContainer *userContainer = seq->private;

    if (userContainer != NULL && userContainer->parent == NULL) {
        return -EPERM;
    }

    return MapWrite(file, buf, size, CAP_SETGID, &userContainer->gidMap,
        &userContainer->parent->gidMap);
}

static int ProcIddMapOpen(struct Vnode *vnode, struct ProcFile *pf)
{
    UserContainer *userContainer = NULL;
    LosProcessCB *process = NULL;
    unsigned int intSave;
    int ret = -EINVAL;
    UINT32 pid;

    SCHEDULER_LOCK(intSave);
    struct ProcDirEntry *pn = (struct ProcDirEntry *)vnode; 
    pid = SimpleStrtoul(pn->parent->name, NULL, 10);

    if (OsProcessIDUserCheckInvalid(pid)) {
        return ret;
    }

    process = OS_PCB_FROM_PID(pid);
    if (process != NULL) {
        userContainer = GetUserContainer(process->credentials->userContainer);
        if (userContainer == NULL) {
            return ret;
        }
    }
    SCHEDULER_UNLOCK(intSave);

    pf->sbuf->private = userContainer;
    return 0;
}

static int ProcUidMapOpen(struct Vnode *vnode, struct ProcFile *pf)
{
    return ProcIddMapOpen(vnode, pf);
}

static int ProcGidMapOpen(struct Vnode *vnode, struct ProcFile *pf)
{
    return ProcIddMapOpen(vnode, pf);
}

static int ProcIdMapRelease(struct Vnode *vnode, struct ProcFile *pf)
{
    struct SeqBuf *seq = pf->sbuf;
    UserContainer *userContainer = seq->private;
    DerefUserContainer(userContainer);
    return 0;
}

static int ProcIdMapRead(struct SeqBuf *seqBuf, void *v)
{
    int ret = 0;
    UserContainer *userContainer = seqBuf->private;

    if (userContainer != NULL && userContainer->parent == NULL) {
        UidGidExtent uidGidExtent = userContainer->uidMap.extent[0];
        ret = LosBufPrintf(seqBuf, "%d %d %u\n", uidGidExtent.first, uidGidExtent.lowerFirst,
            uidGidExtent.count);
    }

    return ret;
}

struct ProcFileOperations g_procUidMapOperations = {
    .open       = ProcUidMapOpen,
    .write      = ProcUidMapWrite,
    .read       = ProcIdMapRead,
    .release    = ProcIdMapRelease,
};

struct ProcFileOperations g_procGidMapOperations = {
    .open       = ProcGidMapOpen,
    .write      = ProcGidMapWrite,
    .read       = ProcIdMapRead,
    .release    = ProcIdMapRelease,
};
#endif
